// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library;

import '../common/names.dart' show Identifiers, Names;
import '../elements/entities.dart';
import 'debug.dart' as debug;
import 'engine.dart';
import 'node_tracer.dart';
import 'type_graph_nodes.dart';

class ClosureTracerVisitor extends TracerVisitor {
  final Iterable<FunctionEntity> tracedElements;
  final List<CallSiteTypeInformation> _callsToAnalyze =
      <CallSiteTypeInformation>[];

  ClosureTracerVisitor(
    this.tracedElements,
    ApplyableTypeInformation tracedType,
    InferrerEngine inferrer,
  ) : super(tracedType, inferrer) {
    assert(
      tracedElements.every((f) => !f.isAbstract),
      "Tracing abstract methods: "
      "${tracedElements.where((f) => f.isAbstract)}",
    );
  }

  @override
  ApplyableTypeInformation get tracedType =>
      super.tracedType as ApplyableTypeInformation;

  void run() {
    analyze();
    if (!continueAnalyzing) return;
    _callsToAnalyze.forEach(_analyzeCall);
    for (FunctionEntity element in tracedElements) {
      inferrer.types.strategy.forEachParameter(element, (Local parameter) {
        ElementTypeInformation info = inferrer.types.getInferredTypeOfParameter(
          parameter,
        );
        info.disableInferenceForClosures = false;
      });
    }
  }

  void _tagAsFunctionApplyTarget([String? reason]) {
    tracedType.mightBePassedToFunctionApply = true;
    if (debug.verbose) {
      print("Closure $tracedType might be passed to apply: $reason");
    }
  }

  void _registerCallForLaterAnalysis(CallSiteTypeInformation info) {
    _callsToAnalyze.add(info);
  }

  void _analyzeCall(CallSiteTypeInformation info) {
    final selector = info.selector!;
    for (var functionElement in tracedElements) {
      if (!selector.callStructure.signatureApplies(
        functionElement.parameterStructure,
      )) {
        continue;
      }
      inferrer.updateParameterInputs(
        info,
        functionElement,
        info.arguments,
        selector,
        remove: false,
        addToQueue: false,
      );
    }
  }

  @override
  void visitClosureCallSiteTypeInformation(
    ClosureCallSiteTypeInformation info,
  ) {
    super.visitClosureCallSiteTypeInformation(info);
    if (info.closure == currentUser) {
      _registerCallForLaterAnalysis(info);
    } else {
      bailout('Passed to a closure');
    }
  }

  @override
  void visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) {
    super.visitStaticCallSiteTypeInformation(info);
    MemberEntity called = info.calledElement;
    if (inferrer.closedWorld.commonElements.isForeign(called)) {
      final name = called.name!;
      if (name == Identifiers.js || name == Identifiers.dartClosureToJS) {
        bailout('Used in JS ${info.debugName}');
      } else if (name == Identifiers.rawDartFunctionRef) {
        bailout('Escaped raw function reference');
      }
    }

    final selector = info.selector;
    if (called.isGetter &&
        selector != null &&
        selector.isCall &&
        inferrer.types.getInferredTypeOfMember(called) == currentUser) {
      // This node can be a closure call as well. For example, `foo()`
      // where `foo` is a getter.
      _registerCallForLaterAnalysis(info);
    }

    final arguments = info.arguments;
    if (_checkIfFunctionApply(called) &&
        arguments != null &&
        arguments.contains(currentUser)) {
      _tagAsFunctionApplyTarget("static call");
    }
  }

  bool _checkIfFunctionApply(MemberEntity element) {
    return inferrer.closedWorld.commonElements.isFunctionApplyMethod(element);
  }

  @override
  void visitDynamicCallSiteTypeInformation(
    DynamicCallSiteTypeInformation info,
  ) {
    super.visitDynamicCallSiteTypeInformation(info);
    final selector = info.selector!;
    final user = currentUser;
    if (selector.isCall) {
      if (info.arguments!.contains(user)) {
        if (info.hasClosureCallTargets || dynamicCallTargetsNonFunction(info)) {
          bailout('Passed to a closure');
        }
        if (info.targets.any(
          (target) => inferrer.memberHierarchyBuilder.anyTargetMember(
            target,
            _checkIfFunctionApply,
          ),
        )) {
          _tagAsFunctionApplyTarget("dynamic call");
        }
      } else {
        if (user is MemberTypeInformation) {
          final currentUserMember = user.member;
          if (info.targets.any(
            (target) => inferrer.memberHierarchyBuilder.anyTargetMember(
              target,
              (element) => element == currentUserMember,
            ),
          )) {
            _registerCallForLaterAnalysis(info);
          }
        }
      }
    } else if (selector.isGetter && selector.memberName == Names.call) {
      // We are potentially tearing off ourself here
      addNewEscapeInformation(info);
    }
  }
}

class StaticTearOffClosureTracerVisitor extends ClosureTracerVisitor {
  StaticTearOffClosureTracerVisitor(
    FunctionEntity tracedElement,
    ApplyableTypeInformation tracedType,
    InferrerEngine inferrer,
  ) : super([tracedElement], tracedType, inferrer);

  @override
  void visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) {
    super.visitStaticCallSiteTypeInformation(info);

    final selector = info.selector;
    if (info.calledElement == tracedElements.first &&
        selector != null &&
        selector.isGetter) {
      addNewEscapeInformation(info);
    }
  }
}
